import re
import numpy as np
import polars as pl
from matplotlib.figure import Figure  # Matplotlib图形对象
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg  # 使用Qt后端的Matplotlib画布
from matplotlib import font_manager
import matplotlib.dates as mdates
import mplfonts
import seaborn as sns
from PySide6.QtWidgets import (QAbstractItemView,
                               QCheckBox, QComboBox, QFileDialog, QFrame, QHBoxLayout,
                               QHeaderView, QLabel, QLineEdit,
                               QPushButton, QScrollArea, QSlider, QSpinBox,
                               QTableWidget, QTableWidgetItem, QTimeEdit, QVBoxLayout,
                               QWidget, QGridLayout)
from PySide6.QtGui import QFont
from PySide6 import QtCore
from PySide6.QtCore import Qt, Signal
from PySide6 import QtWidgets
from typing import Literal
from datetime import date, datetime, time

import os
import sys
sys.path.insert(0, os.path.abspath(
    os.path.join(os.path.dirname(__file__), '..')))
from libs.reverse_raw_channel_data_to_csv import native_read_raw_data
from libs.utils import parse_time
from libs.plot_attribute import PlotAttributes

__project__ = "BL-420N_visualisation"
__version__ = "2.1.1"
__author__ = "hammerklavier@noreply.gitcode.com"
__email__ = "q5vsx3@163.com"
__copyright__ = "Copyright (c) 2025 hammerklavier@noreply.gitcode.com"
__license__ = "GNU GENERAL PUBLIC LICENSE Version 3"
__credits__ = ["RIBBON"]
__status__ = "Prototype"
__issue__ = "https://gitcode.com/hammerklavier/BL-420N_visualisation/issues"


class MyWidget(QWidget):
    send_value_to_warning_widget = Signal(str)

    def __init__(self):
        super().__init__()

        self.resize(1280, 720)
        self.setWindowTitle(f"{__project__} v{__version__}")

        # Title
        self.title_label = QLabel(__project__)
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignHCenter)
        font = QFont()
        font.setPointSize(30)
        self.title_label.setFont(font)
        # self.title.setFont()

        # Author and license
        self.author_label = QLabel(
            f"""\
{__project__} GUI v{__version__}, by {__author__}.
This project follows {__license__}."""
        )
        self.author_label.setAlignment(
            Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
        )
        font = QFont()
        font.setPointSize(10)
        self.author_label.setFont(font)

        # title's seperator
        self.title_seperator_frame = QFrame()
        self.title_seperator_frame.setFrameShape(QFrame.Shape.HLine)
        self.title_seperator_frame.setFrameShadow(QFrame.Shadow.Sunken)

        # Input files and customed labels # This shall be set to a smaller number!
        self.input_files_layout = QVBoxLayout()

        self.input_files_label = QLabel("Input Files")

        self.input_files_table = QTableWidget()
        self.input_files_table.setColumnCount(2)
        self.input_files_table.setHorizontalHeaderLabels(
            ["raw data file", "label"])
        self.input_files_table.setTextElideMode(
            Qt.TextElideMode.ElideNone)  # no effect!
        # self.input_files_table.setColumnWidth(0, 200) # As set to dynamic width, this option no longer functions.
        self.input_files_table.setMinimumHeight(150)
        # Smooth scroll
        self.input_files_table.setHorizontalScrollMode(
            QAbstractItemView.ScrollMode.ScrollPerPixel
        )
        self.input_files_table.setVerticalScrollMode(
            QAbstractItemView.ScrollMode.ScrollPerPixel
        )
        # Dynamic column width
        self.input_files_table.horizontalHeader().setSectionResizeMode(
            0,
            QHeaderView.ResizeMode.ResizeToContents
        )
        self.input_files_table.horizontalHeader().setSectionResizeMode(
            1,
            QHeaderView.ResizeMode.Stretch
        )

        self.input_files_add_button = QPushButton("Add raw files...")
        self.input_files_purge_button = QPushButton("Purge.")

        self.input_files_add_button.clicked.connect(self.add_input_files)
        self.input_files_purge_button.clicked.connect(self.purge_rows_of_table)

        self.input_files_button_layout = QHBoxLayout()
        self.input_files_button_layout.addWidget(self.input_files_add_button)
        self.input_files_button_layout.addWidget(self.input_files_purge_button)

        self.input_files_layout.addWidget(self.input_files_label)
        self.input_files_layout.addWidget(self.input_files_table)
        self.input_files_layout.addLayout(self.input_files_button_layout)

        self.input_files_layout.setStretch(1, 1)

        # Start and end time
        self.start_time_label = QLabel("Start time:")
        self.start_time_edit = QTimeEdit(self)
        self.start_time_edit.setDisplayFormat("HH:mm:ss")

        self.end_time_label = QLabel("End time:")
        self.end_time_edit = QTimeEdit(self)
        self.end_time_edit.setDisplayFormat("HH:mm:ss")

        self.time_grid_layout = QGridLayout()
        self.time_grid_layout.addWidget(self.start_time_label, 0, 0)
        self.time_grid_layout.addWidget(self.start_time_edit, 0, 1)
        self.time_grid_layout.addWidget(self.end_time_label, 1, 0)
        self.time_grid_layout.addWidget(self.end_time_edit, 1, 1)

        # Set plot attributes
        self.plot_attributes_layout = QVBoxLayout()
        # Set plot title
        self.plot_title_label = QLabel("plot title")
        self.plot_title_edit = QLineEdit()
        # Set x-label
        self.plot_x_label = QLabel("plot x label")
        self.plot_x_edit = QLineEdit()
        # Set y-label
        self.plot_y_label = QLabel("plot y label")
        self.plot_y_edit = QLineEdit()

        self.plot_attributes_layout.addWidget(self.plot_title_label)
        self.plot_attributes_layout.addWidget(self.plot_title_edit)
        self.plot_attributes_layout.addWidget(self.plot_x_label)
        self.plot_attributes_layout.addWidget(self.plot_x_edit)
        self.plot_attributes_layout.addWidget(self.plot_y_label)
        self.plot_attributes_layout.addWidget(self.plot_y_edit)

        self.plot_attributes_seperator_frame = QFrame()
        self.plot_attributes_seperator_frame.setFrameShape(QFrame.Shape.HLine)

        # If use original incident
        self.incident_check_box = QCheckBox()
        self.incident_check_box.setText("Use incidents from raw data")

        # Add incidents
        self.add_incident_layout = QVBoxLayout()
        self.add_incident_button_layout = QHBoxLayout()

        self.add_incident_label = QLabel("Add extra incidents")

        self.add_incident_table = QTableWidget()
        self.add_incident_table.setMinimumHeight(150)
        self.add_incident_table.setColumnCount(2)
        self.add_incident_table.setHorizontalHeaderLabels(
            ["time (HH:mm:ss)", "description"])
        # Smooth scroll
        self.add_incident_table.setHorizontalScrollMode(
            QAbstractItemView.ScrollMode.ScrollPerPixel
        )
        self.add_incident_table.setVerticalScrollMode(
            QAbstractItemView.ScrollMode.ScrollPerPixel
        )
        # Resize policy
        self.add_incident_table.horizontalHeader().setSectionResizeMode(
            0,
            QHeaderView.ResizeMode.ResizeToContents
        )
        self.add_incident_table.horizontalHeader().setSectionResizeMode(
            1,
            QHeaderView.ResizeMode.Stretch
        )

        self.add_incident_add_button = QPushButton("Add incident...")
        self.add_incident_purge_button = QPushButton("Purge")

        self.add_incident_add_button.clicked.connect(
            lambda _: self.add_incident_table.insertRow(self.add_incident_table.rowCount()))
        self.add_incident_purge_button.clicked.connect(
            self.purge_rows_of_table)

        self.add_incident_layout.addWidget(self.add_incident_label)
        self.add_incident_layout.addWidget(self.add_incident_table)
        self.add_incident_layout.addLayout(self.add_incident_button_layout)
        self.add_incident_button_layout.addWidget(self.add_incident_add_button)
        self.add_incident_button_layout.addWidget(
            self.add_incident_purge_button)

        self.incident_seperator_frame = QFrame()
        self.incident_seperator_frame.setFrameShape(QFrame.Shape.HLine)

        # Set DPI
        self.dpi_layout = QHBoxLayout()

        self.dpi_label = QLabel()
        self.dpi_label.setText("Display DPI:")

        self.present_dpi_spin_box = QSpinBox()
        self.present_dpi_spin_box.setRange(10, 300)
        self.present_dpi_spin_box.setSingleStep(50)
        self.present_dpi_spin_box.valueChanged.connect(self.refresh_present_dpi_value)

        self.present_dpi_slider = QSlider()
        self.present_dpi_slider.setOrientation(Qt.Orientation.Horizontal)
        self.present_dpi_slider.valueChanged.connect(self.refresh_present_dpi_value)
        self.present_dpi_slider.setRange(10, 300)
        self.present_dpi_slider.setSingleStep(50)
        self.present_dpi_slider.setValue(100)

        self.dpi_layout.addWidget(self.dpi_label)
        self.dpi_layout.addWidget(self.present_dpi_slider)
        self.dpi_layout.addWidget(self.present_dpi_spin_box)

        # Set figure ratio
        self.ratio_layout = QHBoxLayout()

        self.ratio_label = QLabel("Figure ratio:")

        self.ratio_combo_box = QComboBox()
        self.ratio_combo_box.addItems(
            ["1:1", "1.5:1", "2:1", "3:1", "4:1"]
        )
        self.ratio_combo_box.setCurrentIndex(1)

        self.ratio_layout.addWidget(self.ratio_label)
        self.ratio_layout.addWidget(self.ratio_combo_box)

        self.save_path_line = QFrame()
        self.save_path_line.setFrameShape(QFrame.Shape.HLine)
        self.save_path_line.setFrameShadow(QFrame.Shadow.Sunken)

        self.save_dpi_label = QLabel("Save DPI:")
        self.save_dpi_spin_box = QSpinBox()

        self.save_dpi_spin_box.setRange(100, 1200)
        self.save_dpi_spin_box.setSingleStep(25)

        self.save_dpi_slider = QSlider()
        self.save_dpi_slider.setOrientation(Qt.Orientation.Horizontal)
        self.save_dpi_slider.setRange(50, 600)
        self.save_dpi_slider.setSingleStep(50)

        self.save_dpi_slider.valueChanged.connect(self.refresh_save_dpi_value)
        self.save_dpi_spin_box.valueChanged.connect(self.refresh_save_dpi_value)

        self.save_dpi_spin_box.setValue(300)

        self.save_dpi_layout = QHBoxLayout()
        self.save_dpi_layout.addWidget(self.save_dpi_label)
        self.save_dpi_layout.addWidget(self.save_dpi_slider)
        self.save_dpi_layout.addWidget(self.save_dpi_spin_box)


        self.save_button = QPushButton("Save")
        self.save_button.clicked.connect(self.set_save_path)

        # Plot!
        self.plot_button = QPushButton("Plot!")
        self.plot_button.clicked.connect(self.plot)

        # insert matplotlib
        self.plot_container_scroll_area = QScrollArea()
        self.plot_container_scroll_area.setWidgetResizable(True)

        self.plot_container_widget = QWidget()
        self.plot_container_layout = QGridLayout()
        self.plot_container_widget.setLayout(self.plot_container_layout)
        self.plot_container_scroll_area.setWidget(self.plot_container_widget)

        # set up layout
        self.grid_layout = QGridLayout(self)

        self.setting_scroll_area = QScrollArea()
        self.setting_scroll_area.setMinimumWidth(250)
        self.setting_scroll_widget = QWidget()
        self.setting_box_layout = QVBoxLayout(self.setting_scroll_widget)
        self.setting_scroll_area.setWidgetResizable(True)
        self.setting_scroll_area.setWidget(self.setting_scroll_widget)

        self.grid_layout.addWidget(self.title_label, 0, 0, 1, 2)
        self.grid_layout.addWidget(self.author_label, 1, 0, 1, 2)
        self.grid_layout.addWidget(self.title_seperator_frame, 2, 0, 1, 2)
        self.grid_layout.addWidget(self.plot_container_scroll_area, 3, 0)
        self.grid_layout.addWidget(self.setting_scroll_area, 3, 1)
        self.setting_box_layout.addLayout(self.input_files_layout)
        self.setting_box_layout.addLayout(self.time_grid_layout)
        self.setting_box_layout.addLayout(self.plot_attributes_layout)
        self.setting_box_layout.addWidget(self.plot_attributes_seperator_frame)
        self.setting_box_layout.addWidget(self.incident_check_box)
        self.setting_box_layout.addLayout(self.add_incident_layout)
        self.setting_box_layout.addWidget(self.incident_seperator_frame)
        self.setting_box_layout.addLayout(self.dpi_layout)
        self.setting_box_layout.addLayout(self.ratio_layout)
        self.setting_box_layout.addWidget(self.save_path_line)
        self.setting_box_layout.addLayout(self.save_dpi_layout)
        self.setting_box_layout.addWidget(self.save_button)
        self.setting_box_layout.addWidget(self.plot_button)

        self.grid_layout.setRowStretch(0, 0)
        self.grid_layout.setRowStretch(1, 0)
        self.grid_layout.setRowStretch(2, 0)
        self.grid_layout.setRowStretch(3, 1)

        self.grid_layout.setColumnStretch(0, 3)
        self.grid_layout.setColumnStretch(1, 1)

        self._warning_window()

    @QtCore.Slot()
    def add_input_files(self):
        # Create a QFileDialog to select file
        file_dialog = QFileDialog(self)
        # Select only .txt files
        file_dialog.setNameFilter("Text files (*.txt)")
        # Select only existing files
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
        # Get selected files
        if file_dialog.exec():
            file_names = file_dialog.selectedFiles()
            # insert files into the input_files_table
            for file_name in file_names:
                target_row_position = self.input_files_table.rowCount()
                self.input_files_table.insertRow(target_row_position)
                try:
                    relative_path = os.path.relpath(file_name)
                except ValueError:
                    relative_path = file_name
                self.input_files_table.setItem(
                    target_row_position,
                    0,
                    QTableWidgetItem(relative_path)
                )

    def set_save_path(self):
        # Create a QFileDialog to select save path and file name
        file_dialog = QFileDialog(self)
        # Choose png or jpg format
        file_dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
        file_dialog.setFileMode(QFileDialog.FileMode.AnyFile)
        file_dialog.setNameFilters(["PNG image (*.png)", "JPG image (*.jpg)"])
        # Get set path
        # 直接调用 getSaveFileName 并检查返回值
        file_path_name, _ = file_dialog.getSaveFileName(
            self,
            "Save File",
            "",
            "PNG (*.png);;JPG (*.jpg)"
        )

        if file_path_name:  # 如果用户选择了文件并且点击了保存
            self.fig.savefig(file_path_name, dpi=self.save_dpi_spin_box.value())

    @QtCore.Slot()
    def purge_rows_of_table(self):
        sender = self.sender()
        if sender == self.input_files_purge_button:
            table = self.input_files_table
        elif sender == self.add_incident_purge_button:
            table = self.add_incident_table
        else:
            print(f"Error! Unexpected sender: {sender}")
            sys.exit(1)

        # 创建一个列表来存储需要删除的行号，避免在遍历的同时修改表格
        rows_to_remove = set()

        for item in table.selectedItems():
            rows_to_remove.add(item.row())

        # 从最后一个行号开始删除以避免索引问题
        for row in sorted(rows_to_remove, reverse=True):
            table.removeRow(row)

    @QtCore.Slot()
    def refresh_present_dpi_value(self):
        if (sender := self.sender()) == self.present_dpi_slider:
            dpi = self.present_dpi_slider.value()# // 50 * 50
            self.present_dpi_spin_box.setValue(dpi)
        elif sender == self.present_dpi_spin_box:
            dpi = self.present_dpi_spin_box.value()# // 50 * 50
            self.present_dpi_slider.setValue(dpi)

    @QtCore.Slot()
    def refresh_save_dpi_value(self):
        if (sender := self.sender()) == self.save_dpi_slider:
            dpi = self.save_dpi_slider.value()
            self.save_dpi_spin_box.setValue(dpi)
        elif sender == self.save_dpi_spin_box:
            dpi = self.save_dpi_spin_box.value()
            self.save_dpi_slider.setValue(dpi)

    @QtCore.Slot()
    def plot(self):
        # Check if all of the values are valid!
        for row in range(self.add_incident_table.rowCount()):
            print(f"{self.add_incident_table.item(row, 1)}")
            if (self.add_incident_table.item(row, 0) is None
                    or not re.match(
                    r"([0-1][0-9]|2[0-4]:[0-5][0-9]:[0-5][0-9])",
                    self.add_incident_table.item(row, 0).text())  # type: ignore
                    ):
                self.send_value_to_warning_widget.emit(
                    f"Warning: `add_incident table` does not get a valid HH:mm:ss string in {row=}, column=0!"
                )
                self.warning_window.show()
                return
            elif self.add_incident_table.item(row, 1) is None:
                self.send_value_to_warning_widget.emit(
                    f"Warning: You shuold add a description for the incident at {row=}, column=1!"
                )
                self.warning_window.show()
                return

        print("Plot!")
        self.plot_attributes = PlotAttributes(
            input_paths_and_labels=self._get_input_paths_and_labels(),
            start_time=datetime.combine(
                date.today(),
                time(
                    hour=self.start_time_edit.time().hour(),
                    minute=self.start_time_edit.time().minute(),
                    second=self.start_time_edit.time().second(),
                    microsecond=0
                )
            ),
            end_time=datetime.combine(
                date.today(),
                time(
                    hour=self.end_time_edit.time().hour(),
                    minute=self.end_time_edit.time().minute(),
                    second=self.end_time_edit.time().second(),
                    microsecond=0
                )
            ),
            output_path="PLACEHOLDER",
            title=self.plot_title_edit.text(),
            x_label=self.plot_x_edit.text(),
            y_label=self.plot_y_edit.text(),
            raw_incident=self.incident_check_box.isChecked(),
            add_incidents=self._get_datetimes_and_incidents(),  # need correction!
            dpi=self.present_dpi_spin_box.value(),
            ratio=(
                        float(self.ratio_combo_box.currentText().split(":")[0]) /
                        int(self.ratio_combo_box.currentText().split(":")[1])
            )
        )

        # drwa
        # use seaborn style
        sns.set_style("whitegrid")

        # set up fonts
        # add fonts (if Linux system)
        if os.name == "posix":
            print("Add fonts")
            home_dir = os.path.expanduser("~")
            dirs = [f'{home_dir}/.fonts', f'{home_dir}/.Fonts',
                    f'{home_dir}/.local/share/fonts', '/usr/share/fonts']
            for dir in dirs:
                files_triple = os.walk(dir)
                for file_path, _, files in files_triple:
                    for file in files:
                        if file.lower().endswith(".ttf") or file.lower().endswith(".otf") or file.lower().endswith(".ttc"):

                            try:
                                font_manager.fontManager.addfont(
                                    os.path.join(file_path, file))
                            except RuntimeError as e:
                                f"Error: {e}"
                                pass

        # Assign HarmonyOS Sans as default fonts.
        # They are placed under `fonts` directory.
        try:
            font_manager.fontManager.addfont(
                os.path.join(
                    os.path.dirname(__file__),
                    "../fonts/HarmonyOS_Sans_SC_Regular.ttf"
                    )
                )
            font_manager.fontManager.addfont(
                os.path.join(
                    os.path.dirname(__file__),
                    "../fonts/HarmonyOS_Sans_SC_Black.ttf"
                )
            )
            font_manager.fontManager.addfont(
                os.path.join(
                    os.path.dirname(__file__),
                    "../fonts/HarmonyOS_Sans_SC_Bold.ttf"
                )
            )
            font_manager.fontManager.addfont(
                os.path.join(
                    os.path.dirname(__file__),
                    "../fonts/HarmonyOS_Sans_SC_Light.ttf"
                )
            )
            font_manager.fontManager.addfont(
                os.path.join(
                    os.path.dirname(__file__),
                    "../fonts/HarmonyOS_Sans_SC_Medium.ttf"
                )
            )
            font_manager.fontManager.addfont(
                os.path.join(
                    os.path.dirname(__file__),
                    "../fonts/HarmonyOS_Sans_SC_Thin.ttf"
                )
            )
        except Exception as err:
            print(f"Warning: {err}")
        finally:
            mplfonts.use_font("HarmonyOS Sans SC")

        # create new figure and canvas

        # 清除现有的布局内容
        # while self.plot_container_layout.count():
        #     item = self.plot_container_layout.takeAt(0)
        #     widget = item.widget()
        #     if widget is not None:
        #         widget.deleteLater()  # 删除 widget
        #     else:
        #         # 如果有嵌套布局，则递归清理
        #         self._clear_layout(item.layout())

        # self.plot_container_scroll_area = QScrollArea()
        self.plot_container_scroll_area.setWidgetResizable(True)

        self.plot_container_widget = QWidget()
        self.plot_container_layout = QGridLayout()
        self.plot_container_widget.setLayout(self.plot_container_layout)
        self.plot_container_widget.showEvent = lambda event: self.update_canvas_size()


        self.fig = Figure(dpi=self.plot_attributes.dpi)
        self.canvas = FigureCanvasQTAgg(self.fig)

        self.plot_container_layout.addWidget(self.canvas)
        self.plot_container_scroll_area.setWidget(self.plot_container_widget)

        # convert all input files into dataframe
        collection: list[tuple[pl.DataFrame, str, str]] = []
        for file_path, label in self.plot_attributes.input_paths_and_labels:
            df, unit, project_name = native_read_raw_data(file_path)
            if label is None or label == "":
                label = project_name
            collection.append((df, unit, label))

        # prepare for ylim
        y_quantile_min: float = np.inf
        y_quantile_max: float = -np.inf

        # plot
        self.ax = self.fig.gca()
        for df, unit, label in collection:
            df = df.filter(
                (pl.col("timestamp") > self.plot_attributes.start_time)
                & (pl.col("timestamp") < self.plot_attributes.end_time)
            )
            columns = df.columns
            x = df.get_column(columns[0])
            y = df.get_column(columns[1])

            self.ax.plot(x, y, label=label)

            y_quantile_min = min(y_quantile_min, y.quantile(0.1))  # type: ignore
            y_quantile_max = max(y_quantile_max, y.quantile(0.9))  # type: ignore

            if self.plot_attributes.raw_incident:
                incident = df.drop_nulls().select(pl.nth(0), pl.nth(2))
                for row in incident.iter_rows():
                    #self.ax.axvline(row[0], label=row[1], color="k")
                    self.ax.axvline(row[0], color="#2E7E32", linestyle="--", alpha=0.5)

        for incident_time, incident in self.plot_attributes.add_incidents:
            _, ymax = self.ax.get_ylim()
            self.ax.axvline(
                incident_time, # type: ignore
                color="k"
            )
            self.ax.text(
                incident_time, ymax, # type: ignore
                incident,
                verticalalignment="top",
                horizontalalignment="right",
                rotation=90
            )

        if self.plot_attributes.x_label:
            self.ax.set_xlabel(self.plot_attributes.x_label)
        if self.plot_attributes.y_label:
            self.ax.set_ylabel(self.plot_attributes.y_label)
        if self.plot_attributes.title:
            self.ax.set_title(self.plot_attributes.title)

        delta_y = y_quantile_max - y_quantile_min
        self.ax.set_ylim(y_quantile_min - 0.2 * delta_y, y_quantile_max + 0.2 * delta_y)

        self.ax.legend()
        self.ax.tick_params(axis="x", rotation=15)
        self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

        self.fig.set_layout_engine("compressed")
        #self.fig.tight_layout(rect=(0.00, 0.05, 1, 0.95))
        #self.fig.tight_layout()

        self.canvas.draw_idle()


    @staticmethod
    def _clear_layout(layout):
        while layout.count():
            item = layout.takeAt(0)
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
            else:
                MyWidget._clear_layout(item.layout())  # 对于嵌套布局，递归调用

    def update_canvas_size(self):
        container_width = self.plot_container_scroll_area.width()
        container_height = self.plot_container_scroll_area.height()
        if container_width == 0 or container_height == 0:
            # 如果尺寸无效，则稍后再试
            return

        if (container_width / container_height) > self.plot_attributes.ratio:
            canvas_width, canvas_height = container_height * self.plot_attributes.ratio, container_height
        else:
            canvas_width, canvas_height = container_width, container_width / self.plot_attributes.ratio

        self.canvas.setFixedSize(canvas_width * 0.90, canvas_height * 0.90)

    def _get_input_paths_and_labels(self):
        input_paths_and_labels: list[tuple[str, None | str | Literal[""]]] = []
        for row_num in range(self.input_files_table.rowCount()):
            path = self.input_files_table.item(row_num, 0)
            if path is not None:
                path = path.text()
            else:
                print(
                    f"Failed to get content of input_file_table[{row_num}, 0]!")
                sys.exit(1)
            label = self.input_files_table.item(row_num, 1)
            if label is not None:
                label = label.text()
            else:
                print("Label is None!")
            input_paths_and_labels.append((path, label))
        return input_paths_and_labels

    def _get_datetimes_and_incidents(self):
        datetimes_and_incidents: list[tuple[datetime, str | Literal[""]]] = []
        for row_num in range(self.add_incident_table.rowCount()):
            date_time = self.add_incident_table.item(row_num, 0)
            if date_time is None:
                print("Date_time is None!")
                sys.exit(1)
            else:
                date_time = parse_time(date_time.text())
            incident = self.add_incident_table.item(row_num, 1)
            if incident is None:
                print("Description to incident is None!")
                print("This is not expected to happen.")
                sys.exit(1)
            else:
                incident = incident.text()
            datetimes_and_incidents.append((date_time, incident))
        return datetimes_and_incidents

    def _warning_window(self):
        self.warning_window = WarningWidget()
        self.send_value_to_warning_widget.connect(
            self.warning_window.receive_label.setText)


class WarningWidget(QWidget):
    def __init__(self):
        super().__init__()

        self.vlayout = QVBoxLayout(self)

        self.receive_label = QLabel()

        self.vlayout.addWidget(self.receive_label)


if __name__ == "__main__":
    print(
        f"""\
{__project__} GUI v{__version__}, by {__author__}.
This project follows {__license__}.
If you find any bugs, please report it to {__issue__}
""")

    app = QtWidgets.QApplication(sys.argv)
    my_widget = MyWidget()
    my_widget.show()

    sys.exit(app.exec())
